
package com.cosin.dao.neo4j;

import com.cosin.model.po.NodePO;
import com.cosin.model.po.RelationPO;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.Optional;

@Repository
public interface RelationRepository extends Neo4jRepository<RelationPO, Long> {

    /**
     * 保存关系
     * @param relationPO
     * @return
     */
//    @CacheEvict(key = "#a0.projectId", value = "relsOfProjects")
    @Override
    RelationPO save(RelationPO relationPO);

    /**
     * 查找所有关系
     * @param projectId
     * @return
     */
//    @Cacheable(key = "#a0", value = "relsOfProjects")
    @Query("MATCH (n:node)-[r]-(m:node) WHERE n.projectId = $projectId " +
            "RETURN r {.relationId, .name, .source, .target, .width, .projectId, .propertiesAsString, .properties}, m")
    List<RelationPO> findByProjectId(@Param("projectId") int projectId);
    @Query("MATCH (a:node)-[r]-(b:node) " +
            "WHERE a.nodeId IN $nodeIds AND b.nodeId IN $nodeIds " +
            "AND a.projectId = $projectId AND b.projectId = $projectId " +
            "RETURN r {.relationId, .name, .source, .target, .width, .projectId, .propertiesAsString, .properties}")
    List<RelationPO> findRelationsWithinNodeIds(
            @Param("projectId") int projectId,
            @Param("nodeIds") List<Long> nodeIds);


    //    废弃，仅测试用
//    @CacheEvict(key = "#a0.projectId", value = "relsOfProjects")
    @Query("MATCH (n:node {nodeId: $rel.source, projectId: $rel.projectId}) MATCH (m:node {nodeId: $rel.target, projectId: $rel.projectId}) " +
            "CREATE (n)-[r:relation {relationId: $rel.relationId, name: $rel.name, width: $rel.width, projectId: $rel.projectId}]->(m) RETURN r")
    RelationPO saveBySourceAndTarget(@Param("rel") RelationPO relationPO);
    @Query("""
        UNWIND $relations AS rel
        MATCH (a:node {nodeId: rel.source, projectId: $projectId})
        MATCH (b:node {nodeId: rel.target, projectId: $projectId})
        CREATE (a)-[r:relation {
            relationId: rel.relationId,
            name: rel.name,
            width: rel.width,
            projectId: $projectId,
            propertiesAsString: rel.propertiesAsString
        }]->(b)
    """)
    void batchInsertRelations(@Param("projectId") int projectId,
                              @Param("relations") List<RelationPO> relations);


    @Query("MATCH (n:node)-[r]->(m:node) " +
            "WHERE n.nodeId = $source AND m.nodeId = $target AND r.relationId = $relationId AND r.projectId = $projectId " +
            "SET r.name = $name, " +
            "r.width = $width, " +
            "r.propertiesAsString = $propertiesAsString " +
            "RETURN r{.relationId, .name, .source, .target, .width, .projectId, .propertiesAsString, .properties},m")
    RelationPO updateByRel(
            @Param("source") long source,
            @Param("target") long target,
            @Param("relationId") long relationId,
            @Param("projectId") int projectId,
            @Param("name") String name,
            @Param("width") int width,
            @Param("propertiesAsString") String propertiesAsString
    );



    /**
     * 删除关系
     * 根据 关系id，项目id 删除关系
     * @param relationPO
     * @return
     */
//    @CacheEvict(key = "#rel.projectId", value = "relsOfProjects")
    @Query("MATCH (n)-[r:relation {relationId: $rel.relationId, projectId: $rel.projectId}]->(m) DELETE r RETURN count(r)")
    int deleteByRelation(@Param("rel") RelationPO relationPO);
    @Query("MATCH (n)-[r:relation {relationId: $relationId, projectId: $projectId}]->(m) DELETE r RETURN count(r)")
    int deleteByRelation(@Param("relationId") Long relationId, @Param("projectId") int projectId);

//    @CacheEvict(key = "#a0", value = "relsOfProjects")
    @Query("MATCH (n)-[r:relation {projectId: $projectId}]->(m) DELETE r")
    void deleteByProjectId(@Param("projectId") int projectId);

    /**
     * 根据源节点或者目标节点，寻找与之相关的relation
     *
     * @param nodePO 源节点或者目标节点
     * @return 相关的relation列表
     */
    @Query("MATCH (m)-[r]-(n:node {nodeId: $nodePO.nodeId, projectId: $nodePO.projectId}) RETURN r")
    List<RelationPO> listBySourceOrTarget(@Param("nodePO") NodePO nodePO);

    @Query("MATCH (n)-[r:relation {relationId: $relationId, projectId: $projectId}]->(m) RETURN id(r)")
    Long findIdByRelationIdAndProjectId(@Param("relationId") long relationId, @Param("projectId") int projectId);

    /**
     * 根据若干个节点，寻找与之相关的relation
     *
     * @return 相关的relation列表
     */
    @Query("MATCH (a:node)-[r:relation]->(b:node) " +
            "WHERE a.nodeId IN $nodeIds AND b.nodeId IN $nodeIds " +
            "AND a.projectId = $projectId AND b.projectId = $projectId " +
            "RETURN r")
    List<RelationPO> findRelationsBetweenNodes(@Param("nodeIds") List<Long> nodeIds,
                                               @Param("projectId") int projectId);

    /**
     * 根据名称找相关的实体和关系，用于实体查询
     */
    @Query("MATCH (n:node {projectId: $projectId})-[r]-(m) WHERE n.name =~ $sourceName RETURN r")
    List<RelationPO> findBySourceName(@Param("projectId") int projectId, @Param("sourceName") String name);

    /**
     * 用于关系查询
     *
     * @param projectId  项目约束
     * @param name       关系名称
     * @param sourceName 源节点名，若无为.*
     * @param targetName 目标节点名，若无为.*
     */

    @Query("MATCH (n:node)-[r {name: $relName, projectId: $projectId}]->(m:node) " +
            "WHERE n.name =~ $sourceName AND m.name =~ $targetName RETURN r")
    List<RelationPO> findByProjectIdAndNameAndTargetAndSourceFuzzy(@Param("projectId") int projectId, @Param("relName") String name,
                                                                   @Param("sourceName") String sourceName, @Param("targetName") String targetName);

    @Query("MATCH (n:node {projectId: $projectId, nodeId: $sourceId})-[r]-(m:node {nodeId: $targetId}) RETURN r")
    RelationPO findByProjectIdAndTargetIdAndSourceId(@Param("projectId") int projectId,
                                                     @Param("sourceId") int sourceId,
                                                     @Param("targetId") int targetId);

    @Query("MATCH ()-[r {projectId: $projectId}]->() RETURN DISTINCT r.name")
    List<String> findRelationNameByProjectId(@Param("projectId") int projectId);

    @Query("MATCH ()-[r:relation {projectId: $projectId}]->() RETURN COALESCE(MAX(r.relationId), 0)")
    int getMaxRelationId(@Param("projectId") int projectId);
}
